# pita: An invisible audio player
# Copyright (C) 2006-2007 Austin Bingham
#
# This program is free software# you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation# either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY# without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program# if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# You can reach the author at:
#     austin.bingham@gmail.com

import Queue, random, threading, urllib

class Entry:
    '''    An entry in a playlist. It consists of a filename,
    any associated metadata that can be extracted, and
    the results of urllib.urlopen.info()

    raises IOError if filename is bad
    '''

    queue = Queue.Queue()
    metadata_thread = None
    
    def __init__(self, filename):
        self.filename = filename
        self.metadata = {}
        self.mime_type = urllib.urlopen(filename).info().type
        Entry.queue.put(self)

    @staticmethod
    def _get_metadata():
        while (True):
            entry = Entry.queue.get()
            try:
                import extractor
                xtract = extractor.Extractor()
                for key, val in xtract.extract(entry.filename):
                    entry.metadata[str(key.encode('iso-8859-1'))] = str(val.encode('iso-8859-1'))
            except ImportError:
                pass 

    def __repr__(self):
        return '<pita.playlist.Entry %s>' % self.filename

    def __str__(self):
        rval = self.filename
        try:
            rval = '%s (%s -- %s)' % (self['title'],
                                      self['album'],
                                      self['artist'])
        except KeyError:
            pass

        return rval

    def __getitem__(self, key):
        return self.metadata[key]

    def __setitem__(self, key, val):
        self.metadata[key] = val

    def __delitem__(self, key):
        del self.metadata[key]

Entry.metadata_thread = threading.Thread(target = Entry._get_metadata)
Entry.metadata_thread.setDaemon(True)
Entry.metadata_thread.start()

def simple_next(playlist, curr):
    return min(curr + 1, len(playlist))

def random_next(playlist, curr):
        return random.randint(0, len(playlist) - 1)

def looping_next(playlist, curr):
    n = simple_next(playlist, curr)
    if n == len(playlist):
        n = 0
    return n

# TODO: consider using Playlist as the basis for a listener/plugin
# system since it really is the location of most important changes
# in the state of the system. The player could even be a listener
# if we do things right. Hmmm...

class Playlist:
    '''
    A sequence of Entries along with a notion of a current
    Entry and support for iterating over the list.
    '''
    
    def __init__(self):
        self.queue = []
        self.current = 0
        self.looping = False
        self.random = False

    def __len__(self):
        return len(self.queue)

    def __getitem__(self, idx):
        return self.queue[idx]

    # TODO: Reconsider supporting these operations

    #def __setitem__(self, idx, val):
    #    self.queue[idx] = val

    #def __delitem__(self, idx):
    #    del self.queue[idx]

    #def __getslice__(self, i, j):
    #    return self.queue.__getslice__(i, j)

    #def __setslice__(self, i, j, seq):
    #    self.queue.__setslice__(i, j, seq)

    #def __delslice__(self, i, j):
    #    self.queue.__delslice__(i, j)

    def __iter__(self):
        return self.queue.__iter__()

    def __contains__(self, val):
        return self.queue.__contains__(val)

    def append(self, val):
        self.queue.append(val)

    def clear(self):
        del self.queue[:]
        self.current = 0

    def remove(self, pos):
        '''
        Removes the entry at a given position.
        @param pos index of entry to remove
        @type pos int
        @return bool True if this removes the entry that was current at the time of the call, False otherwise
        '''
        if pos >= 0 and pos < len(self):
            del self.queue[pos]

            if pos >= self.current:
                self.current = min(self.current, len(self))

                # See if we just removed the currently playing song. If so,
                # unset the decoder to stop the playing
                if self.current == pos:
                    return True
            else:
                self.current -= 1
                
        return False

    def previous(self):
        '''
        This moves the current index back by one.
        @return bool True if this actually changes the current index, False otherwise
        '''
        old = self.current
        self.current = max(self.current - 1, 0)
        return old != self.current

    def next(self):
        '''
        Determines the next entry to make current in the
        playlist. This takes things like random and looping
        into account. 
        '''
        old = self.current

        if self.random:
            nexter = random_next
        elif self.looping:
            nexter = looping_next
        else:
            nexter = simple_next

        self.current = nexter(self, self.current)

    def jump_to(self, pos):
        '''
        @return bool True if this changes current index, False otherwise
        @throw IndexError if idx is outside the playlist range
        '''
        if pos >= 0 and pos < len(self):
            if pos != self.current:
                self.current = pos
                return True
            return False

        raise IndexError('%s is out of range' % idx)
